七.中断和异常

80386除了保持8086/80386的相关功能外,还增强了中断处理能力,并引入了“异常”的概念。本文 将介绍80386中断和异常的机制。这里下载本文所有源代码

<一>80386的中断和异常

8086/8088把中断分为内部中断和外部中断两大类。为了支持多任务和虚拟存储器等功能,80386把 外部中断称为“中断”,把内部中断称为“异常”。与8086/8088一样,80386通常在两条指令之间 响应中断或异常。80386最多处理256种中断或异常。

1.中断

对80386而言,中断是由异步的外部事件引起的。外部事件及中断响应与正执行的指令没有关系。 通常,中断用于指示I/O设备的一次操作已完成。与8086/8088一样,80386有两根引脚INTR和NMI接 受外部中断请求信号。INTR接受可屏蔽中断请求。NMI接受不可屏蔽中断请求。在80386中,标志寄 存器EFLAGS中的IF标志决定是否屏蔽可屏蔽中断请求。
外部硬件在通过INTR发出中断请求信号的同时,还要向处理器给出一个8位的中断向量。处理器在 响应可屏蔽中断请求时,读取这个由外部硬件给出的中断向量号。处理器对这个中断向量号并没有 规定。但在具体的微机系统中,系统必须通过软件和硬件的配合设置,使得给出的这个中断向量号 不仅与外部中断源对应,而且要避免中断向量号使用冲突情况的出现。可编程中断控制器芯片8259A可 配合80386工作,能够根据设置向处理器提供上述中断向量号,还能处理中断请求的优先级。每 个8259A芯片可以支持8路中断请求信号,如果使用9个8259A芯片(一个主片,8个从片),就可使80386在 单个引脚INTR上接受多达64个中断源的中断请求信号。
处理器不屏蔽来自NMI的中断请求。处理器在响应NMI中断时,不从外部硬件接收中断向量号。 与8086/8088一样,在80386中,不可屏蔽中断所对应的中断向量号固定为2。为了不可屏蔽中断的 嵌套,每当接受一个NMI中断,处理器就在内部屏蔽了再次响应NMI,这一屏蔽过程直到执行中断返回 指令IRET后才结束。所以,NMI处理程序应以IRET指令结束。

2.异常

异常是80386在执行指令期间检测到不正常的或非法的条件所引起的。异常与正执行的指令有直接 的联系。例如,执行除法指令时,除数等于0。再如,执行指令时发现特权级不正确。当发生这些 情况时,指令就不能成功完成。软中断指令“INT n”和“INTO”也归类于异常而不称为中断,这 是因为执行这些指令产生异常事件。
80386识别多种不同类别的异常,并赋予每一种类别以不同的中断向量号。异常发生后,处理器就 象响应中断那样处理异常。即根据中断向量号,转相应的中断处理程序。把这种中断处理程序称为 异常处理程序可能更合适。
根据引起异常的程序是否可被恢复和恢复点不同,把异常进一步分类为故障(Fault)、陷阱(Trap)和 中止(Abort)。我们把对应的异常处理程序分别称为故障处理程序、陷阱处理程序和中止处理程序。
故障是在引起异常的指令之前,把异常情况通知给系统的一种异常。80386认为故障是可排除的。当 控制转移到故障处理程序时,所保存的断点CS及EIP的值指向引起故障的指令。这样,在故障处理程 序把故障排除后,执行IRET返回到引起故障的程序继续执行时,刚才引起故障的指令可重新得到执 行。这种重新执行,不需要操作系统软件的额外参与。故障的发现可能在指令开始执行之前,也可 能在指令执行期间。如果在指令执行期间检测到故障,那么中止故障指令,并把指令的操作数恢复 为指令开始执行之前的值。这可保证故障指令的重新执行得到正确的结果。例如,在一条指令的执 行期间,如果发现段不存在,那么停止该指令的执行,并通知系统产生段故障,对应的段故障处理 程序可通过加载该段的方法来排除故障,之后,原指令就可成功执行,至少不再发生段不存在的故障。
陷阱是在引起异常的指令之后,把异常情况通知给系统的一种异常。当控制转移到异常处理程序时, 所保存的断点CS及EIP的值指向引起陷阱的指令的下一条要执行的指令。下一条要执行的指令,不一 定就是下一条指令。因此,陷阱处理程序并不是总能根据保存的断点,反推确定出产生异常的指令。 在转入陷阱处理程序时,引起陷阱的指令应正常完成,它有可能改变了寄存器或存储单元。软中断 指令、单步异常是陷阱的例子。
中止是在系统出现严重情况时,通知系统的一种异常。引起中止的指令是无法确定的。产生中止时, 正执行的程序不能被恢复执行。系统接收中止后,处理程序要重新建立各种系统表格,并可能重新 启动操作系统。硬件故障和系统表中出现非法值或不一致的值是中止的例子。

3.优先级

在一条指令执行期间,入检测到不只一个中断或异常,那么按下表所列优先级通知系统。把优先级 最高的中断或异常通知系统,其它优先级较低的异常被废弃,而优先级较高的中断则保持悬挂。

80386响应
中断/异常
的优先级
中断/异常类型 优先级
调试故障 最高
其它故障
陷阱指令INT n和INTO
调试陷阱
NMI中断
INTR中断 最低

<二>异常类型

象中断分为多种类型一样,异常也可分为多种类型。

1.80386识别的异常

80386识别的多种不同类别的异常及赋予的对应中断向量号如下表所示。某些异常还以出错码的形式 提供一些附加信息传递给异常处理程序,出错代码列中的“无”表示没有出错代码,“有”表示有 出错代码。













向量号 异常名称 异常类型 出错代码 相关指令
0 除法出错 故障 DIV,IDIV
1 调试异常 故障/陷阱 任何指令
3 单字节INT3 陷阱 INT 3
4 溢出 陷阱 INTO
5 边界检查 故障 BOUNT
6 非法操作码 故障 非法指令编码或操作数
7 设备不可用 故障 浮点指令或WAIT
8 双重故障 中止 任何指令
9 协处理器段越界 中止 访问存储器的浮点指令
0AH 无效TSS异常 故障 JMP、CALL、IRET或中断
0BH 段不存在 故障 装载段寄存器的指令
0CH 堆栈段异常 故障 装载SS寄存器的任何指令、对SS寻址的段访问的任何指令
0DH 通用保护异常 故障 任何特权指令、任何访问存储器的指令
0EH 页异常 故障 任何访问存储器的指令
10H 协处理器出错 故障 浮点指令或WAIT
11H—0FFH 软中断 陷阱 INT n

由上表可见,保护模式下的某些中断向量号的分配与实模式的中断向量号发生了冲突。实 模式下的中断向量号的分配基于PC微机系统的8086/8088 CPU,上表中的中断向量号的分配 是80386所规定的。实际上,Intel在宣布8086/8088时,保留了这些发生冲突的中断向量号。 尽管发生这样的冲突,但以80386为CPU的微机系统仍可保持与以8086/8088为CPU的微机系统 的兼容,原因是在80386的实模式下,几乎不发生那些中断向量号与外部硬件中断请求时所 提供的中断向量号存在冲突的异常。需要注意的是,在保 护模式下必须重新设置8259A中断控制器,以产生不与异常相冲突的硬件中断向量。

2.故障类异常

当发生故障,控制转移到故障处理程序时,所保存的断点CS及EIP的值指向引起故障的指令, 以便在排除故障后恢复执行。

(1)除法出错故障(异常0)

除法出错是一种故障。当执行DIV指令或IDIV指令时,如果除数等于0或者商太大,以至于存 放商的操作数容纳不下,那么就产生这一故障。除法出错故障不提供出错码。

(2)边界检查故障(异常5)

如果BOUND指令发现被测试的值超过了指令中给定的范围,那么就发生边界检查故障。边界 检查故障不提供出错码。

(3)非法操作码故障(异常6)

如果80386不能把CS和EIP所指向存储单元的位模式识别为某条指令的部分,那么就发生非 法操作码故障。当出现如下情况时,发生这样的故障:(1)操作码字段的内容不是一个合法 的80386指令代码;(2)要求使用存储器操作数的场合,使用了寄存器操作数;(3)不能被加 锁的指令前使用了LOCK前缀。非法操作码故障不提供操作码。

(4)设备不可用故障(异常7)

设备不可用故障支持80387数字协处理器。在没有80387协处理器硬件的系统中,可用该异 常的处理程序代替协处理器的软件模拟器。在发生任务切换时,使得只有在新任务使用浮 点指令时,才进行80387寄存器状态的切换。设备不可用故障不提供出错码。该故障在下列 情况下产生:(1)在执行浮点指令时,控制寄存器CR0中的EM位或TS位为1;(2)在执行WAIT指 令时,控制寄存器CR0中TS位及EM位都为1。需要注意的 是,本异常的处理程序必须是一个过程而不能是任务,否则当处理程序发布一条IRET指令 时,80386就设置TS位。然后协处理器再次执行这个发生故障的指令,发现TS是置位的,因 此就再次发生异常7,结果是无休止的循环。处理程序能通过陷阱门被调用,因为执行期间 可以允许中断。

(5)无效TSS故障(异常0AH)

当正从任务状态段TSS装入选择子时,如果发生了除了段不存在故障以外的段异常时,就发 生无效TSS故障。在进入故障处理程序时,保存的CS及EIP指向发生故障的指令;或者该故障 作为任务切换的一部分发生时,指向任务的第一条指令。
无效TSS故障提供了一个出错码,出错码的格式如下图所示。其中选择子部分是指向引起故 障的TSS的选择子。16位的出错代码的主要成分是选择子,指向引起故障的TSS的选择子。 高13位是选择子的索引部分,TI位是描述符表指示位。

出错代码的格式
BIT15—BIT3 BIT2 BIT1 BIT0
选择子的索引部分 TI IDT EXT

上图所示出错码格式是异常时出错码的一般格式。从图中可见出错码中不含选择子的RPL, 而由IDT位和EXT位代替。当处理某一异常或外部中断时,又发生了某种异常,那么EXT位 置1。当从中断描述符表IDT中读出表项并产生异常时,IDT位置1,这只在中断或异常的处 理期间才会发生。当没有选择子时,构成出错码选择子部分的值为0。
一些引起无效TSS故障的原因如下:
TSS描述符中的段限长小于103;
无效的LDT描述符,或者LDT未出现;
堆栈段不是一个可写段;
堆栈段选择子索引的描述符超出描述符表界限;
堆栈段DPL与新的CPL不匹配;
堆栈段选择子的RPL不等于CPL;
代码段选择子索引的描述符超出描述符表界限;
代码段选择子不指向代码段;
非一致代码段的DPL不等于新的CPL;
一致代码段的DPL大于新的CPL;
对应DS、ES、FS或GS的选择子指向一个不可读段(如系统段);
对应DS、ES、FS或GS的选择子索引的描述符超出描述符表的界限。

(6)段不存在故障(异常0BH)

处理器在把描述符装入非SS段寄存器的高速缓冲时,如果发现描述符其它方面有效,而P位 为0(表示对应段不存在),那么在引用此描述符时就发生段不存在故障。有关SS段的情形纳 入堆栈段故障。在进入故障处理程序时,保存的CS及EIP执行发生故障的指令;或者该故障 作为任务切换的一部分发生时,指向任务的第一条指令。
段不存在故障提供了一个包含引起该故障的段选择子的出错代码。出错码的格式如上图所 示。选择子索引部分为引起段不存在故障的段描述符选择子的索引。

(7)堆栈段故障(异常0CH)

当处理器检测到用SS寄存器进行寻址的段有关的某种问题时,就发生堆栈段故障。在进入 故障处理程序时,保存的CS及EIP指向发生故障的指令;或者该故障作为任务切换的一部分 发生时,指向任务的第一条指令。堆栈段故障提供一个出错码,出错码的格式也如上图。
具体地说,当出现下列三种情况时,将引起堆栈段故障:
(1)在堆栈操作时,偏移超出段界限所规定的范围。这种情况下的出错码是0。例如PUSH操 作时,堆栈溢出。
(2)在由特权级变换所引起的对内层堆栈的操作时,偏移超出段界限所规定的范围。这种情 况下的出错码包含有内层堆栈的选择子。
(3)装入到SS寄存器(高速缓冲寄存器)的描述符中的存在位为0。这种情况下的出错码包含 有对应的选择子。
上述第一种情况是容易辨别的。第二和第三种情况的辨别要通过判断出错代码所含选择子 所指示的描述符中的存在位进行。如果存在位为1,那么是第二种情况;否则是第三种情况。

(8)通用保护故障(异常0DH)

除了明确列出的段异常外,其它的段异常都被视为通用保护故障。在进入故障处理程序时, 保存的CS及EIP指向发生故障的指令;或者该故障作为任务切换的一部分发生时,指向任务 的第一条指令。通用保护故障提供一个出错码,出错码的格式也如上图所示。
根据处理程序可能作出的响应,通用保护故障可分为如下两类:
(1)违反保护方式,但程序无须中止的异常。这类故障提供的出错码为0。这种异常在应用 程序执行特权指令或I/O访问时发生,支持虚拟8086程序的系统或支持虚拟I/O访问的系统 需要模拟这些指令,并在模拟完成产生故障的指令后,重新执行被中断的程序。
(2)违反保护方式,并导致程序终止的异常。这类故障提供的出错码可能为0,也可能不 为0(能确定选择子时)。引起这类故障的一些原因如下:
向某个只读数据段或代码段写;
从某个只能执行的代码段读出;
将某个系统段描述符装入到数据段寄存器DS、ES、FS、GS或SS;
将控制转移到一个不可执行的段;
在通过段寄存器CS、DS、ES、FS或GS访问内存时,偏移超出段界限;
当访问某个描述符表时,超过描述符表段界限;
把PG位为1但PE位为0的控制信息装入到CR0寄存器;
切换到一个正忙的任务。
对上述两类通用保护故障的辨别,可通过检查引起故障的指令和出错码进行。如果出错码 非0,那么肯定是第二类通用保护故障。如果出错码是0,那么需要进一步检查引起故障的 指令,以确定它是否是系统支持的可以模拟的指令。

(9)页故障(异常0EH)

关于页故障的详细说明请见后面的文章。

(10)协处理器出错(异常10H)

协处理器出错故障指示协处理器发生了未被屏蔽的数字错误,如上溢或下溢。在引起故障 的浮点指令之后的下一条浮点指令或WAIT指令,把协处理器出错作为一个故障通知给系统。 协处理器出错故障不提供出错码。

3.陷阱类异常

(1)调试陷阱(异常1)

调试异常有故障类型,也有陷阱类型。调试程序可以访问调试寄存器DR6,以确定调试异常 的原因和类型。调试异常不提供出错码。

(2)单字节INT3(异常3)

INT3是一条特别的单字节“INT n”指令。调试程序可利用该指令支持程序断点。INT3指令 被看成是一种陷阱,而不是一个中断。当由于执行INT3指令进入异常3处理程序时,被保存 的CS和EIP指向紧跟INT3的指令,即INT3后面的字节。INT3陷阱不提供出错码。

(3)溢出(异常4)

INTO指令提供条件陷阱。如果OF标志为1,那么INTO指令产生陷阱;否则不产生陷阱,继续 执行INTO后面的指令。在进入溢出处理程序时,被保存的CS和EIP指向INTO指令的下一条指 令。溢出陷阱不提供出错码。

4.中止类异常

(1)双重故障异常(异常8)

当系统正在处理一个异常时,如果又检测到一个异常,处理器试图向系统通知一个双重故障, 而不是通知第二个异常。双重故障属于中止类异常,所以在转入双重故障处理程序时,被保 存的CS和EIP可能不指向引起双重故障的指令,而且指令的重新启动不支持双重故障。双重故 障提供的出错码为0。
当正处理一个段故障异常时,有可能又产生一个页故障。在这种情况下,通知给系统的是一 个页故障异常而不是双重故障异常。但是,如果正处理一个段故障或页故障时,又一个段故 障被检测到;或者如果正处理一个页故障时,又一个页故障被检测到,那么就引起双重故障。
当正处理一个双重故障时,又一个段或页故障被检测到,那么处理器暂停执行指令,并进入 关机方式。关机方式类似于处理器指令一条HLT指令后的状态:处理器空转,并维持到处理器 接收到一个NMI中断请求或者被重新启动为止。在关机方式下,处理器不响应INTR中断请求。
双重故障通常指示系统表出现严重的问题,例如段描述符表、页表或中断描述符表出现问题。 双重故障处理程序在重建系统表后,可能不得不重新启动操作系统。

(2)协处理器段越界(异常9)

协处理器段越界异常属于中止类异常,这是因为引起该异常的指令不能被重新启动。当浮点 指令操作数超出段界限时,产生该中止异常。协处理器段越界异常不提供出错码。在异常处 理程序入口保存的CS及EIP指向被中止的指令。这种中止不是系统的中止,而是只影响检测到 这种异常时正执行的指令所在的程序。

<三>中断和异常的转移方法

80386实模式下的中断和异常的转移方法与8086相同。这里介绍的中断和异常的转移方法是指 80386在保护模式下响应中断和处理异常时所采用的转移方法。

1.中断描述符表IDT

与8086/8088一样,在响应中断或者处理异常时,80386根据中断向量号转对应的处理程序。但 是,在保护模式下,80386不使用实模式下的中断向量表,而是使用中断描述符表IDT。在保护 模式下,80386把中断向量号作为中断描述符表IDT中描述符的索引,而不再是中断向量表中的 中断向量的索引。
象全局描述符表GDT一样,在整个系统中,中断描述符表IDT只有一个。中断描述符表寄存 器IDTR指示IDT在内存中的位置。由于80386只识别256个中断向量号,所以IDT最大长度是2K。
中断描述符表IDT所含的描述符只能是中断门、陷阱门和任务门。也就是说,在保护模式 下,80386只有通过中断门、陷阱门或任务门才能转移到对应的中断或异常处理程序。
从前文中给出的门描述符的格式可见,门描述符包含由选择子和偏移构成的48位全指针。另 外,双字计数字段对中断门、陷阱门和任务门而言无意义。

2.中断响应和异常处理的步骤

由硬件自动实现的中断响应和异常处理的步骤如下:
首先,判断中断向量号要索引的门描述符是否超出IDT的界限。若超出界限,就引起通用保护 故障,出错码为中断向量号乘8再加2。
其次,从IDT中取得对应的门描述符,分解出选择子、偏移量和描述符属性类型,并进行有关 检查。描述符只能是任务门、286中断门、286陷阱门、386中断门或386陷阱门,否则就引起 通用保护故障,出错码是中断向量号乘8再加2。如果是由INT n指令或INTO指令引起转移,还 要检查中断门、陷阱门或任务门描述符中的DPL是否满足CPL<=DPL(对于其它的异常或中断, 门中的DPL被忽略)。这种检查可以避免应用程序执行INT n指令时,使用分配给各种设备用的 中断向量号。如果检查不通过,就引起通用保护故障,出错码是中断向量号乘8再加2。门描 述符中的P位必须是1,表示门描述符是一个有效项,否则就引起段不存在故障,出错码是中 断向量号乘8再加2。
最后,根据门描述符类型,分情况转入中断或异常处理程序。
对于异常处理,在开始上述步骤之前,还要根据异常类型确定返回点;如果有出错代码,则形 成符合出错码格式的出错码,并在实际执行异常处理程序之前把出错码压入堆栈。为了保证栈 的双字边界对齐,16位的出错码以32位的值压入,其中高16位的值未作定义,对于16位段也是 如此。

3.通过中断门或陷阱门的转移

如果中断向量号所指示的门描述符是386中断门或386陷阱门,那么控制转移到当前任务的一个 处理程序过程,并且可以变换特权级。与其它调用门的CALL指令一样,从中断门和陷阱门中获 取指向处理程序的48位全指针。其中16位选择子是对应处理程序或代码段的选择子,它指示全 局描述符表GDT或局部描述符表LDT中的代码段描述符;32位偏移指示处理程序入口点在代码段 内的偏移量。
通过中断门或陷阱门的转移过程如下所示,该过程由 硬件自动进行
(1)若选择子为空,则产生通用保护故障;
(2)取对应的描述符;
(3)若非存储段描述符,则产生通用保护故障;
(4)若非一致代码段且DPL
(5)调整RPL=0;
(6)把描述符装入CS;
(7)若入口偏移越界,则产生通用保护故障;
(8)EFLAGS压入堆栈;
(9)CS压入堆栈;
(10)EIP压入堆栈;
(11)使TF=0,NT=0;
(12)若为中断门,则使IF=0;
(13)若有出错码,则把出错码压入堆栈;
(14)转入处理程序。
由上述转移过程可见,中断门或陷阱门中指示处理程序的选择子必须指向描述一个可执行的 代码段的描述符。如果选择子为空,就引起通用保护故障,出错码是0。如果描述符不是代码 段描述符,就引起通用保护故障,出错码含选择子。
中断或异常可以转移到同一特权级或内层特权级。上述指定处理程序代码段的描述符中的类 型及DPL字段,决定了这种同一任务内的转移是否要发生特权级变换。如果是一个非一致代码 段,并且DPLCPL则产生通用保护异常。
上述转移过程中的第六步,也就是“把描述符装入CS”,是指把上述指定处理程序段的描述符 装入CS的高速缓冲寄存器中,在这一步骤中要对描述符进行类似通过调用门进行转移的其它检 查,包括是否代码段描述符和代码段描述符是否存在等,因此可能再发生异常。在对该描述符 进行检查时,通过调整门中选择子的RPL=0(在处理器内部调整,而不影响存储器中的选择子 的RPL字段)的方法,实现只考虑代码段的DPL,而不考虑门中选择子的RPL。把描述符装入CS之 后,还要检查门描述符中给出的表示处理程序代码段入口的偏移是否越界,即是否超出段界限。 如果越界,就引起出错码为0的通用保护故障。
从转移过程还可以看出,把标志寄存器和断点压入堆栈的做法和顺序与实模式是相同的,但这 里每一次堆栈操作是一个双字,CS被扩展成32位。在16位段中亦是如此。
把TF置成0,表示不允许处理程序单步执行。把NT置成0,表示处理程序在利用中断返回指 令IRET返回时,返回到同一任务而不是一个嵌套任务。需要注意的是,任何特权级的程序都可 改变NT位,这样可以利用中断或陷阱处理程序完成任务切换。
通过中断门的转移和通过陷阱门的转移之间的差别只是对IF标志的处理。对于中断门,在转移 过程中把IF置为0,使得在处理程序执行期间屏蔽掉INTR中断(当然,在中断处理程序中可以人 为设置IF标志打开中断,以使得在处理程序执行期间允许响应可屏蔽中断);对于陷阱门,在 转移过程中保持IF位不变,即如果IF位原来是1,那么通过陷阱门转移到处理程序之后仍允 许INTR中断。因此,中断门最适宜于处理中断,而陷阱门适宜于处理异常。
在有出错码的情况下,转入处理程序之前还要把出错码压入堆栈。只有异常处理才可能有出错 码。下图给出了通过中断门或陷阱门转移时的堆栈情况。(a)是没有变换特权级和没有出错码 的情形;(b)是没有变换特权级有出错码的情形;(c)是变换特权级和没有出错码的内层堆栈的 情形。(d)是变换特权级和有出错码的内层堆栈情形。注意图中每一项为双字。

4.通过任务门的转移

如果中断向量号所只是的门描述符是任务门描述符,那么控制转移到一个作为独立的任务方式 出现的处理程序。任务门中的选择子是指向描述对应处理程序任务的TSS段的选择子,即该选择 子指示一个可用的286TSS或386TSS。通过任务门的转移与通过任务门到一个可用的386TSS的段 间调用指令CALL的转移很相似,主要的区别是,对于提供出错码的异常处理,在完成任务切换 之后,把出错码压入新任务的堆栈中(通过任务门进行转移时,返回地址和外层栈指针不压入新 任务的堆栈)。
通过任务门的转移,在进入中断或异常处理程序时,标志寄存器EFLAGS中的NT位被置为1,表示 是嵌套任务,则IRET指令返回时,沿TSS中的链接字段返回到最后一个被挂起的任务。
在响应中断或处理异常时,使用任务门可提供一个处理程序任务的自动调度。这种任务调度由 硬件直接执行,并且越过包含在操作系统中的软件任务切换,这就为处理程序提供了一个快速 的任务切换。

5.转移方法的比较

对中断的响应和异常的处理,80386允许通过使用中断门或陷阱门实现由当前任务之内的一个过 程进行处理;也允许通过使用任务门实现由另一个任务进行处理。在当前任务之内的处理程序 较为简单,并可以很快地转移到处理程序,但处理程序要负责保存及恢复处理器的寄存器等内 容。转到不同任务的处理程序要花费较长时间,保存及恢复处理器寄存器内容的开销作为任务 切换的一部分。使用当前任务内的处理程序的方法,在响应中断或处理异常时,对正执行任务 的状态可直接进行访问,但是这就要求每一个任务之内都包含一个处理程序。使用独立任务的 处理方法,使处理程序得到较好的隔离,但在响应中断或处理异常时,对原任务状态的访问变 得较为复杂。需要注意得是,有些异常必须由中断门或陷阱门进行处理,如上面提到得异常7。
无效TSS异常必须使用任务门进行处理,以保证处理程序有一个有效得任务环境。其它得异常通 常在任务环境之内进行处理。在任务内,异常被检测并且不必屏蔽中断,所以,所以使用陷阱 门。由陷阱门指示的异常处理程序是一个由所有任务共享的过程,所以该处理程序最好置于全 局地址空间之内。如果各个任务要求有不同的处理程序,那么全局异常处理程序可保存一个各 处理程序的入口表,并为引起异常的任务调用相应的处理程序。
中断通常与正执行的任务没有关系,并可能从使用任务门提供的隔离中获得好处。要求较快响 应的中断,通过中断门可以得到较好的处理。因为中断随时都可能发生,所以,通过中断门访 问的中断处理程序,必须置于全局地址空间中,以便对所有的任务都有效。还需强调的 是,80386程序绝不能调用一个低特权级的过程,当处理器调用一个中断或异常处理程序时,它 实施相同的规则,所以,这样一个过程必须至少具有与在其任务上下文中被调用的、由任务所 执行的最高特权级的过程相同的特权级。因此,在使用中断门时,中断处理程序通常必须被安 排在特权级0,否则若正在特权级0执行时发生中断,则不能进入中断处理程序,而会引起通用 保护故障(中断处理程序使用任务门时除外,因为任务切换可以从任何特权级切换到目标任务的 任何特权级)。

6.中断或异常处理后的返回

中断返回指令IRET用于从中断或异常处理程序的返回。该指令的执行根据任务嵌套标志NT位是 否为1分为两种情形。
NT位为1,表示是嵌套任务的返回。当前TSS中的链接字段保存有前一任务的TSS的选择子,取出 该选择子进行任务切换就完成了返回。这种情形在由通过任务门转入的中断或异常处理程序返 回时出现,因为在由中断门或陷阱门转入处理程序时,NT位已被清0。
NT位为0,表示当前任务内的返回。这种情形在由通过中断门或陷阱门转入的中断或异常处理程 序返回时出现。具体进行的操作包括:从堆栈顶弹出返回指针EIP及CS,然后弹出EFLAGS值。弹 出的CS选择子的RPL字段,确定返回后的特权级。如果返回选择子的RPL与CPL相同,则不进行特 权级改变。若RPL规定了一个外层特权级,则需要特权级改变,从内层堆栈中弹出外层堆栈的指 针ESP及SS的值。这些做法与RET指令相似。例如,使用CS选择子的RPL,而不是由选择子标识的 段的DPL,是为了返回到不在DPL给定特权级的一致代码段。若弹出的CS的选择子的RPL规定了一 个内层特权级,则产生通用保护故障。需要注意的是,对于IRET指令,保存在当前堆栈中的返回 地址中的选择子字段必须指向代码段描述符。而不能是系统段或门描述符。否则将引起通用保护 故障。
对于提供出错代码的异常处理程序,必须先人为地从堆栈中弹出出错代码,然后再执行IRET指令, 及出错代码不会自动被处理器弹出或取消。
中断返回指令IRET不仅能够用于由中断/异常引起的嵌套任务的返回,而且也适用于由段间调用 指令CALL通过任务门引起的嵌套任务的返回,如前文所述,在执行通过任务门进行任务切换的段 间调用指令CALL时,标志寄存器中的NT位被置为1,表示任务嵌套。而RET指令不能实现此功能。

<四>演示中断处理的实例(实例六)

下面给出一个用于演示中断处理的实例。该实例的逻辑功能是,在屏幕的左上角以倒计时方式显 示秒为单位的时间,在时间用完后结束。该实例演示内容包括:外部中断处理程序和陷阱处理程序。

1.源程序组织和清单

本实例由如下几部分组成:
(1)全局描述符表GDT。GDT中除了含有常见的几个描述符外,还含有描述时钟中断处理程序所使用 的代码段和数据段描述符,以及描述显示程序所使用的代码段和数据段描述符。
(2)中断描述符表IDT。为了在保护模式下响应中断和处理异常,必须有IDT。IDT含有256个门描述 符。8号安排的是一个通向时钟中断处理程序的中断门,0FEH号安排的是通向显示处理程序的陷阱 门,其它均安排成通向其它中断或异常处理程序的陷阱门。
(3)时钟中断处理程序的代码段和数据段。
(4)实现直接写显示缓冲区进行显示的程序代码段和数据段。
(5)处理其它中断或异常的处理程序的代码段。
(6)演示程序的代码段、数据段和堆栈段。
(7)实模式下执行的启动和结束程序代码段和数据段。
源程序清单如下:
;名称:ASM6.ASM
;功能:演示中断处理的实现
;编译:TASM ASM6.ASM
;连接:TLINK ASM6.OBJ
;----------------------------------------------------------------------------
INCLUDE         386SCD.INC
;----------------------------------------------------------------------------
;部分常量定义
;----------------------------------------------------------------------------
EOICOM          =       20h                       ;外部中断处理结束命令
ICREGP          =       20h                       ;中断控制寄存器端口地址
IMREGP          =       21h                       ;中断屏蔽寄存器端口地址
;----------------------------------------------------------------------------
GDTSeg          SEGMENT PARA USE16                ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
                ;全局描述符表GDT
GDT             LABEL   BYTE
                ;空描述符
DUMMY           Desc    <>
                ;规范段描述符
Normal          Desc    <0ffffh,,,ATDW,,>
                ;视频缓冲区段描述符(DPL=3)
VideoBuf        Desc    <0ffffh,8000h,0bh,ATDW,,>
;----------------------------------------------------------------------------
EFFGDT          LABEL   BYTE
                ;临时代码段描述符
TempCode        Desc    <0ffffh,TempCodeSeg,,ATCE,,>
                ;演示代码段描述符
DemoCode        Desc    <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
                ;演示数据段描述符
DemoData        Desc    <DemoDataLen-1,DemoDataSeg,,ATDW,,>
                ;演示堆栈段描述符
DemoStack       Desc    <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
                ;0feh号中断处理程序(显示程序)代码段描述符
EchoCode        Desc    <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
                ;0feh号中断处理程序(显示程序)数据段描述符
EchoData        Desc    <EchoDataLen-1,EchoDataSeg,,ATDW,,>
                ;8号中断处理程序代码段描述符
TICode          Desc    <TICodeLen-1,TICodeSeg,,ATCE,,>
                ;8号中断处理程序数据段描述符
TIData          Desc    <TIDataLen-1,TIDataSeg,,ATDW,,>
                ;其它中断或异常处理程序代码段描述符
Other           Desc    <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
;----------------------------------------------------------------------------
GDTLen          =       $-GDT                     ;全局描述符表长度
GDNum           =       ($-EFFGDT)/(SIZE Desc)    ;需特殊处理的描述符数
;----------------------------------------------------------------------------
Normal_Sel      =       Normal-GDT                ;规范段描述符选择子
Video_Sel       =       VideoBuf-GDT              ;视频缓冲区段描述符选择子
;----------------------------------------------------------------------------
TempCode_Sel    =       TempCode-GDT              ;临时代码段的选择子
DemoCode_Sel    =       DemoCode-GDT              ;演示代码段的选择子
DemoData_Sel    =       DemoData-GDT              ;演示数据段的选择子
DemoStack_Sel   =       DemoStack-GDT             ;演示堆栈段的选择子
EchoCode_Sel    =       EchoCode-GDT              ;0feh号中断程序代码段选择子
EchoData_Sel    =       EchoData-GDT              ;0feh号中断程序数据段选择子
TICode_Sel      =       TICode-GDT                ;8号中断程序代码段选择子
TIData_Sel      =       TIData-GDT                ;8号中断程序数据段选择子
Other_Sel       =       Other-GDT                 ;其它中断或异常代码段选择子
;----------------------------------------------------------------------------
GDTSeg          ENDS                              ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg          SEGMENT PARA USE16                ;中断描述符表数据段(16位)
;----------------------------------------------------------------------------
IDT             LABEL   BYTE                      ;中断描述符表
                ;0--7的8个陷阱门描述符
                REPT    8
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
                ENDM
                ;对应8号(时钟)中断处理程序的门描述符
                Gate    <TIBegin,TICode_Sel,,AT386IGate,>
                ;从9--0fdh的245个陷阱门描述符
                REPT    245
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
                ENDM
                ;对应0feh号中断处理程序的陷阱门描述符
                Gate    <EchoBegin,EchoCode_Sel,,AT386TGate,>
                ;对应0ffh号中断处理程序的陷阱门描述符
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
;----------------------------------------------------------------------------
IDTLen          =       $-IDT
;----------------------------------------------------------------------------
IDTSeg          ENDS                              ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段
;----------------------------------------------------------------------------
OtherCodeSeg    SEGMENT PARA USE16
                ASSUME  CS:OtherCodeSeg
;----------------------------------------------------------------------------
OtherBegin      PROC    FAR
                mov     ax,Video_Sel
                mov     es,ax
                mov     ah,17h                    ;在屏幕左上角显示兰底白字
                mov     al,'!'                    ;符号"!"
                mov     WORD PTR es:[0],ax
                jmp     $                         ;无限循环
OtherBegin      ENDP
;----------------------------------------------------------------------------
OtherCodeLen    =       $
OtherCodeSeg    ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的数据段
;----------------------------------------------------------------------------
TIDataSeg       SEGMENT PARA USE16
Count           DB      0                         ;中断发生的计数器
TIDataLen       =       $
TIDataSeg       ENDS
;----------------------------------------------------------------------------
;8号中断处理程序的代码段
;----------------------------------------------------------------------------
TICodeSeg       SEGMENT PARA USE16
                ASSUME  CS:TICodeSeg,DS:TIDataSeg
;----------------------------------------------------------------------------
TIBegin         PROC    FAR
                push    eax                       ;保护现场
                push    ds
                push    fs
                push    gs
                mov     ax,TIData_Sel             ;置中断处理程序数据段
                mov     ds,ax
                mov     ax,EchoData_Sel           ;置显示过程数据段
                mov     fs,ax
                mov     ax,DemoData_Sel           ;置演示程序数据段
                mov     gs,ax
                cmp     Count,0
                jnz     TI2                       ;计数非0表示未到1秒
                mov     Count,18                  ;每秒约18次
                int     0feh                      ;调用0FEH号中断处理程序显示
                cmp     BYTE PTR fs:Mess,'0'
                jnz     TI1
                mov     BYTE PTR gs:Flag,1        ;显示符号'0'时置标记
TI1:            dec     BYTE PTR fs:Mess          ;调整显示符号
TI2:            dec     Count                     ;调整计数
                pop     gs                        ;恢复现场
                pop     fs
                pop     ds
                mov     al,EOICOM                 ;通知中断控制器中断处理结束
                out     ICREGP,al
                pop     eax
                iretd                             ;中断返回
TIBegin         ENDP
;----------------------------------------------------------------------------
TICodeLen       =       $
TICodeSeg       ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序数据段
;----------------------------------------------------------------------------
EchoDataSeg     SEGMENT PARA USE16
Mess            DB      '8',4eh
EchoDataLen     =       $
EchoDataSeg     ENDS
;----------------------------------------------------------------------------
;0FEH号中断处理程序(显示程序)的代码段
;----------------------------------------------------------------------------
EchoCodeSeg     SEGMENT PARA USE16
                ASSUME  CS:EchoCodeSeg,DS:EchoDataSeg
;----------------------------------------------------------------------------
EchoBegin       PROC    FAR
                push    ax                        ;保护现场
                push    ds
                push    es
                mov     ax,EchoData_Sel           ;置显示过程数据段
                mov     ds,ax
                mov     ax,Video_Sel              ;置视频缓冲区数据段
                mov     es,ax
                mov     ax,WORD PTR Mess
                mov     WORD PTR es:[0],ax
                pop     es
                pop     ds
                pop     ax
                iretd
EchoBegin       ENDP
;----------------------------------------------------------------------------
EchoCodeLen     =       $
EchoCodeSeg     ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
;----------------------------------------------------------------------------
DemoStackSeg    SEGMENT PARA USE16
DemoStackLen    =       1024
                DB      DemoStackLen DUP(0)
DemoStackSeg    ENDS
;----------------------------------------------------------------------------
;演示任务的数据段
;----------------------------------------------------------------------------
DemoDataSeg     SEGMENT PARA USE16
Flag            DB      0
DemoDataLen     =       $
DemoDataSeg     ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
;----------------------------------------------------------------------------
DemoCodeSeg     SEGMENT PARA USE16
                ASSUME  CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin       PROC    FAR
                mov     ax,DemoStack_Sel          ;置堆栈
                mov     ss,ax
                mov     sp,DemoStackLen           ;置数据段
                mov     ax,DemoData_Sel
                mov     ds,ax
                mov     es,ax
                mov     fs,ax
                mov     gs,ax
                mov     al,11111110b              ;置中断屏蔽字
                out     IMREGP,al                 ;只开发时钟中断
                sti                               ;开中断
DemoConti:      cmp     BYTE PTR Flag,0           ;判标志
                jz      DemoConti                 ;直到不为0
                cli                               ;关中断
                ;转回临时代码段,准备回实方式
                JUMP16  TempCode_Sel,<OFFSET ToDos>
DemoBegin       ENDP
;----------------------------------------------------------------------------
DemoCodeLen     =       $
DemoCodeSeg     ENDS
;----------------------------------------------------------------------------
TempCodeSeg     SEGMENT PARA USE16                ;临时任务的代码段
                ASSUME  CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual         PROC    FAR
                JUMP16  DemoCode_Sel,DemoBegin    ;转演示任务
ToDos:          mov     ax,Normal_Sel             ;恢复实方式段描述符高速缓存
                mov     ds,ax
                mov     es,ax
                mov     fs,ax
                mov     gs,ax
                mov     ss,ax
                mov     eax,cr0                   ;准备返回实模式
                and     al,11111110b
                mov     cr0,eax
                JUMP16  <SEG Real>,<OFFSET Real>
Virtual         ENDP
;----------------------------------------------------------------------------
TempCodeSeg     ENDS
;============================================================================
RDataSeg        SEGMENT PARA USE16                ;实方式数据段
VGDTR           PDesc   <GDTLen-1,>               ;GDT伪描述符
VIDTR           PDesc   <IDTLen-1,>               ;IDT伪描述符
NORVIDTR        PDesc   <3ffh,>                   ;用于保存原IDTR值
SPVar           DW      ?                         ;用于保存实方式下的SP
SSVar           DW      ?                         ;用于保存实方式下的SS
IMaskRegV       DB      ?                         ;用于保存原中断屏蔽寄存器值
RDataSeg        ENDS
;----------------------------------------------------------------------------
RCodeSeg        SEGMENT PARA USE16                ;实方式代码段
                ASSUME  CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start           PROC
                mov     ax,RDataSeg
                mov     ds,ax
                cld
                call    InitGDT                   ;初始化全局描述符表GDT
                call    InitIDT                   ;初始化中断描述符表IDT
                mov     SSVar,ss                  ;保存堆栈指针
                mov     SPVar,sp
                sidt    QWORD PTR NORVIDTR        ;保存IDTR
                in      al,IMREGP
                mov     BYTE PTR IMaskRegV,al
                lgdt    QWORD PTR VGDTR           ;装载GDTR
                cli                               ;关中断
                lidt    QWORD PTR VIDTR           ;装载IDTR
                mov     eax,cr0
                or      al,1
                mov     cr0,eax
                JUMP16  <TempCode_Sel>,<OFFSET Virtual>
Real:           mov     ax,RDataSeg
                mov     ds,ax
                lss     sp,DWORD PTR SPVar        ;又回到实方式
                lidt    QWORD PTR NORVIDTR
                mov     al,IMaskRegV
                out     IMREGP,al
                sti
                mov     ax,4c00h
                int     21h
Start           ENDP
;----------------------------------------------------------------------------
InitGDT         PROC
                push    ds
                mov     ax,GDTSeg
                mov     ds,ax
                mov     cx,GDNum
                mov     si,OFFSET EFFGDT
InitG:          mov     ax,[si].BaseL
                movzx   eax,ax
                shl     eax,4
                shld    edx,eax,16
                mov     WORD PTR [si].BaseL,ax
                mov     BYTE PTR [si].BaseM,dl
                mov     BYTE PTR [si].BaseH,dh
                add     si,SIZE Desc
                loop    InitG
                pop     ds
                mov     bx,16
                mov     ax,GDTSeg
                mul     bx
                mov     WORD PTR VGDTR.Base,ax
                mov     WORD PTR VGDTR.Base+2,dx
                ret
InitGDT         ENDP
;----------------------------------------------------------------------------
InitIDT         PROC
                mov     bx,16
                mov     ax,IDTSeg
                mul     bx
                mov     WORD PTR VIDTR.Base,ax
                mov     WORD PTR VIDTR.Base+2,dx
                ret
InitIDT         ENDP
;----------------------------------------------------------------------------
RCodeSeg        ENDS
                END     Start

2.关于实例六的说明

(1)时钟中断仍使用8H号中断向量

为了即简单又清楚地演示在保护模式下响应外部中断并进行处理,实例使用了时钟中断源, 但没有通过重新设置中断控制器的方法改变对应的中断向量。所以,时钟中断使用的8H号中 断向量号就与双重故障异常使用的中断向量号发生冲突。但实例仅是演示程序,所以只要保 证不发生双重故障异常,就可避免冲突,从而不会影响演示。
设置中断屏蔽寄存器,仅开放时钟中断。所以,在开中断状态下,也只可能发生时钟中断, 而不会发生其它外部中断。

(2)时钟中断处理程序的设计

由于通过中断门转时钟中断处理程序,所以在控制转移时不发生任务切换。但外部中断随时 可能发生,因此中断处理程序必须采取保护现场等措施。作为演示程序,该中断处理程序检 查和调整在其数据段中的计数器,满18次后,就认为已满一秒,再调整用于显示的倒计数信 息。如果倒计数信息为0,那么就设置演示程序数据段中的时间为0标志。该中断处理程序通 过约定的数据区与显示程序及演示程序交换信息。

(3)利用一个软中断(陷阱处理)程序实现显示

为了演示陷阱及其处理,把显示过程安排成陷阱处理程序。上述时钟中断处理程序通过软中 断调用指令INT调用该显示程序,以显示倒计数。在控制转移时,也没有任务切换。该陷阱 处理程序相当于一个“软中断”处理程序,类似实模式下的BIOS中断INT 10H。

(4)对其它中断或异常的响应

为了简单,除了8H号和0FEH号外,IDT中其它的门均通向一个处理程序。该处理程序用于处 理其它中断或异常。处理过程也极其简单,在屏幕左上角显示蓝底白字的符号“!”,然后 进入无限循环。实际上,按演示程序现在的安排,不可能发生这种情况。

(5)没有特权级变换

为了简单,实例涉及的中断处理程序和异常处理程序都保持特权级0。所以,控制转移时不 发生特权级变换。因此,没有使用其它堆栈。

(6)对IDT的初始化

由于IDT中门描述符没有32位段基地址,并且入口点偏移较小,所以就直接填写门描述符结 构变量,没有额外再初始化。过程InitIDT只是设置IDT伪描述符。

(7)装载和保存IDTR寄存器

再使IDT发挥作用之前,还要装载中断描述符表寄存器IDTR;但为了回到实模式后,恢复原 来的IDTR之内容,所以先保存IDTR的内容。实例使用如下指令保存IDTR:
    sidt    QWORD PTR NORVIDTR
该指令的功能是把IDTR的内容保存到存储器中的伪描述符NORVIDTR中。该伪描述符的结构如 前文所述的结构类型PDESC所示,低字是以字节为单位的界限,高双字是基地址。在后面的文 章中将对SIDT指令作详细说明。
本实例使用如下指令装载IDTR寄存器:
    lidt    QWORD PTR VIDTR
    lidt    QWORD PTR NORVIDTR
LIDT指令类似于LGDT指令,在后面的文章中将对LIDT指令作详细说明。

<五>演示异常处理的实例(实例七)

下面给出一个用于模拟异常和演示异常处理的实例。该实例的逻辑功能是,在屏幕上显示一条 提示用户以按键方式选择异常类型的字符,然后模拟指定的异常。该实例演示内容包括:除法 出错故障处理、溢出陷阱处理、段不存在故障处理、堆栈段出错处理和通用保护故障处理;还 有作为一个独立任务方式出现的陷阱处理程序。

1.源程序组织和清单

为了演示以独立任务方式出现的陷阱处理程序,实例含有两个任务:演示任务和读键盘任务。 实例由如下几部分组成:
(1)全局描述符表GDT和中断描述符表IDT;
(2)读键盘任务局部描述符表、任务状态段、堆栈段和代码段等;
(3)演示任务的局部描述符表、任务状态段、堆栈段、代码段和数据段等;
(4)作为演示任务一部分的有关陷阱处理和故障处理程序的代码段;
(5)作为演示任务一部分的显示出错码过程的代码段;
(6)实模式下执行的启动和结束程序代码段和数据段。
在切换到保护模式后,就进入临时代码段。为了简单,演示任务不发生特权级变换。演示步骤 如下:
(1)从临时代码段转移到演示代码段。
(2)做演示准备。把演示任务的LDT选择子装入LDTR,并填入TSS,装载任务寄存器TR,建立演示 任务堆栈,设置其它数据段寄存器。
(3)接收要模拟的异常类型号。通过软中断指令INT调用读键盘任务完成该步骤。读键盘任务只 有在接收到指定的字符后才结束。接收的字符是0、4、B、C和D。
按接收的字符模拟异常。即根据键入的字符,执行有关程序片段。在这些片段中,有意安排了 能引起有关故障或陷阱的指令。
结束演示,转临时代码段,返回DOS。
程序清单如下:
;名称:ASM7.ASM
;功能:模拟异常和演示异常处理
;编译:TASM ASM7.ASM
;连接:TLINK ASM7.OBJ
;----------------------------------------------------------------------------
INCLUDE         386SCD.INC
;----------------------------------------------------------------------------
GDTSeg          SEGMENT PARA USE16                ;全局描述符表数据段(16位)
;----------------------------------------------------------------------------
                ;全局描述符表GDT
GDT             LABEL   BYTE
                ;空描述符
DUMMY           Desc    <>
                ;规范段描述符及选择子
Normal          Desc    <0ffffh,,,ATDW,,>
Normal_Sel      =       Normal-GDT
                ;视频缓冲区段描述符(DPL=3)及选择子
VideoBuf        Desc    <0ffffh,8000h,0bh,ATDW,,>
VideoBuf_Sel    =       VideoBuf-GDT
;----------------------------------------------------------------------------
EFFGDT          LABEL   BYTE
                ;临时代码段描述符及选择子
TempCode        Desc    <0ffffh,TempCodeSeg,,ATCE,,>
TempCode_Sel    =       TempCode-GDT
                ;演示代码段描述符及选择子
DemoCode        Desc    <DemoCodeLen-1,DemoCodeSeg,,ATCE,,>
DemoCode_Sel    =       DemoCode-GDT
                ;演示任务局部描述符表段描述符及选择子
DemoLDT         Desc    <DemoLDTLen-1,DemoLDTSeg,,ATLDT,,>
DemoLDT_Sel     =       DemoLDT-GDT
                ;演示任务TSS段描述符及选择子
DemoTSS         Desc    <DemoTSSLen-1,DemoTSSSeg,,AT386TSS,,>
DemoTSS_Sel     =       DemoTSS-GDT
                ;缓冲数据段描述符及选择子
XBuffer         Desc    <BufferLen-1,BufferSeg,,ATDW,,>
XBuffer_Sel     =       XBuffer-GDT
                ;读键盘任务局部描述符表段描述符及选择子
GKeyLDT         Desc    <GKeyLDTLen-1,GKeyLDTSeg,,ATLDT,,>
GKeyLDT_Sel     =       GKeyLDT-GDT
                ;读键盘任务TSS段描述符及选择子
GKeyTSS         Desc    <GKeyTSSLen-1,GKeyTSSSeg,,AT386TSS,,>
GKeyTSS_Sel     =       GKeyTSS-GDT
                ;显示陷阱处理程序代码段描述符及选择子
EchoCode        Desc    <EchoCodeLen-1,EchoCodeSeg,,ATCE,,>
EchoCode_Sel    =       EchoCode-GDT
                ;显示出错码过程代码段描述符及选择子
SubCode         Desc    <SubCodeLen-1,SubCodeSeg,,ATCE,,>
SubCode_Sel     =       SubCode-GDT
                ;其它中断或异常处理程序代码段描述符及选择子
Other           Desc    <OtherCodeLen-1,OtherCodeSeg,,ATCE,,>
Other_Sel       =       Other-GDT
;----------------------------------------------------------------------------
GDTLen          =       $-GDT                     ;全局描述符表长度
GDNum           =       ($-EFFGDT)/(SIZE Desc)    ;需处理基地址的描述符个数
;----------------------------------------------------------------------------
GDTSeg          ENDS                              ;全局描述符表段定义结束
;----------------------------------------------------------------------------
IDTSeg          SEGMENT PARA USE16                ;中断描述符表数据段(16位)
;----------------------------------------------------------------------------
IDT             LABEL   BYTE                      ;中断描述符表
                ;0号陷阱门描述符(对应除法出错故障)
                Gate    <DivBegin,Divide_Sel,,AT386TGate,>
                ;从1--3的3个陷阱门描述符
                REPT    3
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
                ENDM
                ;4号陷阱门描述符(对应溢出陷阱)
                Gate    <OFBegin,OF_Sel,,AT386TGate,>
                ;从5--0ah的的6个陷阱门描述符
                REPT    6
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
                ENDM
                ;0bh号陷阱门描述符(对应段不存在故障)
                Gate    <SNPBegin,SNP_Sel,,AT386TGate,>
                ;0ch号陷阱门描述符(对应堆栈段故障)
                Gate    <SSEBegin,SSE_Sel,,AT386TGate,>
                ;0dh号陷阱门描述符(对应通用保护故障)
                Gate    <GPBegin,GP_Sel,,AT386TGate,>
                ;从0eh--0edh的240个陷阱门描述符
                REPT    240
                Gate    <OtherBegin,Other_Sel,,AT386TGate,>
                ENDM
                ;对应0feh号陷阱门描述符(对应显示中断处理程序)
                Gate    <EchoBegin,EchoCode_Sel,,AT386TGate,>
                ;0ffh号任务门描述符(对应读键盘中断处理任务)
                Gate    <,GKeyTSS_Sel,,ATTaskGate,>
;----------------------------------------------------------------------------
IDTLen          =       $-IDT
;----------------------------------------------------------------------------
IDTSeg          ENDS                              ;中断描述符表段定义结束
;----------------------------------------------------------------------------
;读键盘任务局部描述符表段
;----------------------------------------------------------------------------
GKeyLDTSeg      SEGMENT PARA USE16
;----------------------------------------------------------------------------
GLDT            LABEL   BYTE
                ;代码段描述符及选择子
GKeyCode        Desc    <0ffffh,GKeyCodeSeg,,ATCE,,>
GKeyCode_Sel    =       GKeyCode-GLDT+TIL
                ;堆栈段描述符及选择子
GKeyStack       Desc    <GKeyStackLen-1,GKeyStackSeg,,ATDWA,,>
GKeyStack_Sel   =       GKeyStack-GLDT+TIL
;----------------------------------------------------------------------------
GKeyLDNum       =       ($-GLDT)/(SIZE Desc)      ;需初始化基地址的描述符个数
GKeyLDTLen      =       $                         ;局部描述符表段长度
;----------------------------------------------------------------------------
GKeyLDTSeg      ENDS
;----------------------------------------------------------------------------
;读键盘任务TSS段
;----------------------------------------------------------------------------
GKeyTSSSeg      SEGMENT PARA USE16
                DD      0                         ;链接字
                DD      ?                         ;0级堆栈指针
                DW      ?,?
                DD      ?                         ;1级堆栈指针
                DW      ?,?
                DD      ?                         ;2级堆栈指针
                DW      ?,?
                DD      0                         ;CR3
                DW      GKeyBegin,0               ;EIP
                DD      0                         ;EFLAGS
                DD      0                         ;EAX
                DD      0                         ;ECX
                DD      0                         ;EDX
                DD      0                         ;EBX
                DW      GKeyStackLen,0            ;ESP
                DD      0                         ;EBP
                DD      0                         ;ESI
                DD      0                         ;EDI
                DW      Normal_Sel,0              ;ES
                DW      GKeyCode_Sel,0            ;CS
                DW      GKeyStack_Sel,0           ;SS
                DW      Normal_Sel,0              ;DS
                DW      Normal_Sel,0              ;FS
                DW      Normal_Sel,0              ;GS
                DW      GKeyLDT_Sel,0             ;LDTR
                DW      0                         ;调试陷阱标志
                DW      $+2                       ;指向I/O许可位图的偏移
                DB      0ffh                      ;I/O许可位图结束字节
GKeyTSSLen      =       $
GKeyTSSSeg      ENDS
;----------------------------------------------------------------------------
;读键盘任务堆栈段
;----------------------------------------------------------------------------
GKeyStackSeg    SEGMENT PARA USE16
GKeyStackLen    =       1024
                DB      GKeyStackLen DUP(0)
GKeyStackSeg    ENDS
;----------------------------------------------------------------------------
;读键盘任务代码段
;----------------------------------------------------------------------------
GKeyCodeSeg     SEGMENT PARA USE16
                ASSUME  CS:GKeyCodeSeg,DS:RDataSeg,ES:BufferSeg
;----------------------------------------------------------------------------
GKeyBegin       PROC    FAR
                push    ds
                push    es
                push    fs
                push    gs
                mov     ax,Normal_Sel
                mov     ss,ax                     ;准备转实方式
                mov     eax,cr0
                and     al,11111110b
                mov     cr0,eax                   ;转实方式
                JUMP16  <SEG GetKey>,<OFFSET GetKey>
GetKey:         mov     ax,RDataSeg               ;实方式
                mov     ds,ax
                mov     ebp,esp                   ;恢复实方式部分现场
                lss     sp,DWORD PTR SPVar
                lidt    QWORD PTR NORVIDTR
                sti
                mov     dx,OFFSET Mess
                mov     ah,9
                int     21h                       ;显示提示信息
GetKey1:        mov     ah,0
                int     16h                       ;读键盘
                cmp     al,'0'
                jz      GetKey2
                cmp     al,'4'
                jz      GetKey2
                and     al,11011111b              ;小写转大写
                cmp     al,'B'
                jb      GetKey1
                cmp     al,'D'
                ja      GetKey1                   ;只有[0,4,b,c,d]有效
GetKey2:        mov     dl,al
                mov     ah,2
                int     21h                       ;显示所按字符
                mov     ax,BufferSeg
                mov     es,ax
                mov     BYTE PTR es:KeyASCII,dl   ;保存到缓冲数据段
                cli                               ;准备返回保护方式
                lidt    QWORD PTR VIDTR
                mov     eax,cr0
                or      al,1
                mov     cr0,eax
                JUMP16  <GKeyCode_Sel>,<OFFSET GetKeyV>
GetKeyV:        mov     ax,GKeyStack_Sel          ;又进入保护方式
                mov     ss,ax
                mov     esp,ebp
                pop     gs
                pop     fs
                pop     es
                pop     ds
                iretd
                jmp     GKeyBegin
GKeyBegin       ENDP
;----------------------------------------------------------------------------
GKeyCodeLen     =       $
GKeyCodeSeg     ENDS
;----------------------------------------------------------------------------
;其它中断或异常处理程序的代码段
;----------------------------------------------------------------------------
OtherCodeSeg    SEGMENT PARA USE16
                ASSUME  CS:OtherCodeSeg
;----------------------------------------------------------------------------
OtherBegin      PROC    FAR
                mov     si,OFFSET MessOther
                int     0feh                      ;显示提示信息
                mov     WORD PTR es:[0],ax
                jmp     $                         ;进入无限循环
OtherBegin      ENDP
;----------------------------------------------------------------------------
OtherCodeLen    =       $
OtherCodeSeg    ENDS
;----------------------------------------------------------------------------
;除法出错故障处理程序代码段
;----------------------------------------------------------------------------
DivCodeSeg      SEGMENT PARA USE16
                ASSUME  CS:DivCodeSeg
;----------------------------------------------------------------------------
DivBegin        PROC    FAR
                mov     si,OFFSET Mess0
                mov     di,0
                int     0feh                      ;显示提示信息
                shr     ax,1                      ;处理模拟的除法错误
                iretd                             ;返回
DivBegin        ENDP
;----------------------------------------------------------------------------
DivCodeLen      =       $
DivCodeSeg      ENDS
;----------------------------------------------------------------------------
;溢出陷阱处理程序代码段
;----------------------------------------------------------------------------
OFCodeSeg       SEGMENT PARA USE16
                ASSUME  CS:OFCodeSeg
;----------------------------------------------------------------------------
OFBegin         PROC    FAR
                mov     si,OFFSET Mess4
                mov     di,0
                int     0feh                      ;显示提示信息
                iretd                             ;返回
OFBegin         ENDP
;----------------------------------------------------------------------------
OFCodeLen       =       $
OFCodeSeg       ENDS
;----------------------------------------------------------------------------
;段不存在故障处理程序代码段
;----------------------------------------------------------------------------
SNPCodeSeg      SEGMENT PARA USE16
                ASSUME  CS:SNPCodeSeg
;----------------------------------------------------------------------------
SNPBegin        PROC    FAR
                mov     si,OFFSET MessB
                mov     di,0
                int     0feh                      ;显示提示信息
                pop     eax                       ;弹出出错代码
                CALL16  SubCode_Sel,SubBegin      ;显示出错代码
                pop     eax
                add     eax,2                     ;按模拟的引起段不存在指令
                push    eax                       ;调整返回地址
                iretd
SNPBegin        ENDP
;----------------------------------------------------------------------------
SNPCodeLen      =       $
SNPCodeSeg      ENDS
;----------------------------------------------------------------------------
;堆栈段故障处理程序代码段
;----------------------------------------------------------------------------
SSECodeSeg      SEGMENT PARA USE16
                ASSUME  CS:SSECodeSeg
;----------------------------------------------------------------------------
SSEBegin        PROC    FAR
                mov     si,OFFSET MessC
                mov     di,0
                int     0feh                      ;显示提示信息
                pop     eax                       ;弹出出错代码
                CALL16  SubCode_Sel,SubBegin      ;显示出错代码
                pop     eax
                add     eax,4                     ;按模拟的引起堆栈段错误的
                push    eax                       ;指令调整返回地址
                iretd
SSEBegin        ENDP
;----------------------------------------------------------------------------
SSECodeLen      =       $
SSECodeSeg      ENDS
;----------------------------------------------------------------------------
;通用保护故障处理程序代码段
;----------------------------------------------------------------------------
GPCodeSeg       SEGMENT PARA USE16
                ASSUME  CS:GPCodeSeg
;----------------------------------------------------------------------------
GPBegin         PROC    FAR
                push    ebp
                mov     ebp,esp
                push    eax
                push    esi
                push    edi                       ;保护现场
                mov     si,OFFSET MessD
                mov     di,0
                int     0feh                      ;显示提示信息
                mov     eax,[bp+4]                ;从堆栈中取出出错代码
                CALL16  SubCode_Sel,SubBegin      ;显示出错代码
                pop     edi
                pop     esi
                pop     eax                       ;恢复部分现场
                add     DWORD PTR [ebp+8],2       ;按模拟的故障指令调整返回
                pop     ebp                       ;地址
                add     esp,4                     ;废除堆栈中的出错代码
                iretd
GPBegin         ENDP
;----------------------------------------------------------------------------
;显示出错码过程代码段
;----------------------------------------------------------------------------
SubCodeSeg      SEGMENT PARA USE16
                ASSUME  CS:SubCodeSeG
;----------------------------------------------------------------------------
SubBegin        PROC                              ;AX中含出错代码
                push    ax                        ;保护现场
                push    cx
                push    dx
                push    si
                push    di
                mov     si,OFFSET ErrCode
                mov     dx,ax
                mov     cx,4
SubR1:          rol     dx,4                      ;把16位出错代码转换成4位
                mov     al,dl                     ;十六进制数的ASCII码并保存
                and     al,0fh
                add     al,30h
                cmp     al,'9'
                jbe     SubR2
                add     al,7
SubR2:          mov     [si],al
                inc     si
                loop    SubR1
                mov     si,OFFSET ErrMess
                Mov     di,80*2                   ;从第二行行首开始
                int     0feh                      ;显示出错码
                pop     di                        ;恢复现场
                pop     si
                pop     dx
                pop     cx
                pop     ax
                retf                              ;返回
SubBegin        ENDP
;----------------------------------------------------------------------------
SubCodeLen      =       $
SubCodeSeg      ENDS
;----------------------------------------------------------------------------
GPCodeLen       =       $
GPCodeSeg       ENDS
;----------------------------------------------------------------------------
;实现显示的陷阱处理程序代码段
;入口参数--DS:SI指向显示信息串,ES:DI指向显示缓冲区
;----------------------------------------------------------------------------
EchoCodeSeg     SEGMENT PARA USE16
                ASSUME  CS:EchoCodeSeg
;----------------------------------------------------------------------------
EchoBegin       PROC    FAR
                pushad                            ;保护现场
                cld
                mov     ah,7
                mov     al,20h
                mov     cx,80
                push    di
                rep     stosw                     ;清所在显示行
                pop     di
Echo1:          lodsb
                or      al,al
                jz      Echo2
                stosw                             ;显示指定信息串
                jmp     Echo1
Echo2:          popad                             ;恢复现场
                iretd
EchoBegin       ENDP
;----------------------------------------------------------------------------
EchoCodeLen     =       $
EchoCodeSeg     ENDS
;----------------------------------------------------------------------------
;缓冲区数据段
;----------------------------------------------------------------------------
BufferSeg       SEGMENT PARA USE16
KeyASCII        DB      ?
Buffer          DB      128 DUP(?)
BufferLen       =       $
BufferSeg       ENDS
;----------------------------------------------------------------------------
;演示任务局部描述符表段
;----------------------------------------------------------------------------
DemoLDTSeg      SEGMENT PARA USE16
;----------------------------------------------------------------------------
DLDT            LABEL   BYTE
                ;演示任务TSS段作为数据段的描述符及选择子
ToDemoTSS       Desc    <DemoTSSLen-1,DemoTSSSeg,,ATDW,,>
ToDemoTSS_Sel   =       ToDemoTSS-DLDT+TIL
                ;演示任务堆栈段描述符及选择子
DemoStack       Desc    <DemoStackLen-1,DemoStackSeg,,ATDWA,,>
DemoStack_Sel   =       DemoStack-DLDT+TIL
                ;演示任务数据段描述符及选择子
DemoData        Desc    <DemoDataLen-1,DemoDataSeg,,ATDW,,>
DemoData_Sel    =       DemoData-DLDT+TIL
                ;除法出错故障处理程序代码段描述符及选择子
Divide          Desc    <DivCodeLen-1,DivCodeSeg,,ATCE,,>
Divide_Sel      =       Divide-DLDT+TIL
                ;溢出陷阱处理程序代码段描述符及选择子
OverFlow        Desc    <OFCodeLen-1,OFCodeSeg,,ATCE,,>
OF_Sel          =       OverFlow-DLDT+TIL
                ;段不存在故障处理程序代码段描述符及选择子
SNPCode         Desc    <SNPCodeLen-1,SNPCodeSeg,,ATCE,,>
SNP_Sel         =       SNPCode-DLDT+TIL
                ;堆栈段出错故障处理程序代码段描述符及选择子
SSECode         Desc    <SSECodeLen-1,SSECodeSeg,,ATCE,,>
SSE_Sel         =       SSECode-DLDT+TIL
                ;通用保护故障处理程序代码段描述符及选择子
GPCode          Desc    <GPCodeLen-1,GPCodeSeg,,ATCE,,>
GP_Sel          =       GPCode-DLDT+TIL
                ;为模拟段不存在故障而安排的数据段描述符及选择子
TestNPS         Desc    <0ffffh,,,ATDW-80h,,>
TestNPS_Sel     =       TestNPS-DLDT+TIL
;----------------------------------------------------------------------------
DemoLDNum       =       ($-DLDT)/(SIZE Desc)      ;LDT描述符个数
DemoLDTLen      =$
;----------------------------------------------------------------------------
DemoLDTSeg      ENDS
;----------------------------------------------------------------------------
;演示任务TSS段
;----------------------------------------------------------------------------
DemoTSSSeg      SEGMENT PARA USE16
DemoTaskSS      TSS     <>
                DB      0ffh
DemoTSSLen      =       $
DemoTSSSeg      ENDS
;----------------------------------------------------------------------------
;演示任务的堆栈段
;----------------------------------------------------------------------------
DemoStackSeg    SEGMENT PARA USE16
DemoStackLen    =       1024
                DB      DemoStackLen DUP(0)
DemoStackSeg    ENDS
;----------------------------------------------------------------------------
;演示任务的数据段
;----------------------------------------------------------------------------
DemoDataSeg     SEGMENT PARA USE16
Mess0           DB      'Divide Error (Exception 0)',0
Mess4           DB      'Overflow (Exception 4)',0
MessB           DB      'Segment Not Present (Exception 11)',0
MessC           DB      'Stack Segment (Exception 12)',0
MessD           DB      'General Protection (Exception 13)',0
MessOther       DB      'Other Exception',0
ErrMess         DB      'Error Code = '
ErrCode         DB      4 DUP(0),'H',0
DemoDataLen     =       $
DemoDataSeg     ENDS
;----------------------------------------------------------------------------
;演示任务的代码段
;----------------------------------------------------------------------------
DemoCodeSeg     SEGMENT PARA USE16
                ASSUME  CS:DemoCodeSeg,DS:DemoDataSeg
;----------------------------------------------------------------------------
DemoBegin       PROC    FAR
                mov     ax,DemoLDT_Sel
                lldt    ax                        ;装载LDTR
                mov     ax,DemoStack_Sel          ;置堆栈
                mov     ss,ax
                mov     esp,DemoStackLen
                mov     ax,ToDemoTSS_Sel
                mov     gs,ax                     ;把演示任务LDT选择子填入TSS
                mov     WORD PTR gs:DemoTaskSS.TRLDTR,DemoLDT_Sel
                mov     ax,DemoTSS_Sel
                ltr     ax                        ;装载TR
                mov     ax,DemoData_Sel
                mov     ds,ax                     ;装载其它数据段寄存器
                mov     ax,VideoBuf_Sel
                mov     es,ax
                mov     ax,XBuffer_Sel
                mov     fs,ax
                mov     ax,XBuffer_Sel
                mov     gs,ax
                int     0ffh                      ;接收要模拟的异常类型号
                mov     al,BYTE PTR fs:KeyASCII   ;按接收的字符模拟异常号
                cmp     al,'0'
                jnz     Demo4
                mov     ax,2000
                mov     cl,2                      ;模拟除法出错故障
                div     cl                        ;该指令长2字节
                jmp     Over
Demo4:          cmp     al,'4'
                jnz     Demo11
                mov     al,100
                add     al,50
                into                              ;模拟溢出陷阱
                JMP     OVER
Demo11:         cmp     al,'B'
                jnz     Demo12
                mov     ax,TestNPS_Sel            ;模拟段不存在故障
                mov     gs,ax                     ;该指令长2字节
                JMP     Over
Demo12:         cmp     al,'C'
                jnz     Demo13
                mov     ebp,esp                   ;模拟堆栈出错故障
                mov     al,[ebp]                  ;该指令长4字节
                jmp     Over
Demo13:         mov     ax,DemoTSS_Sel            ;模拟通用保护故障
                mov     gs,ax                     ;该指令长2字节
Over:           ;转临时代码段
                JUMP16  TempCode_Sel,<OFFSET ToDos>
DemoBegin       ENDP
;----------------------------------------------------------------------------
DemoCodeLen     =       $
DemoCodeSeg     ENDS
;----------------------------------------------------------------------------
TempCodeSeg     SEGMENT PARA USE16                ;临时任务的代码段
                ASSUME  CS:TempCodeSeg
;----------------------------------------------------------------------------
Virtual         PROC    FAR
                JUMP16  DemoCode_Sel,DemoBegin    ;转演示任务
ToDos:          mov     ax,Normal_Sel             ;恢复实方式段描述符高速缓存
                mov     ds,ax
                mov     es,ax
                mov     fs,ax
                mov     gs,ax
                mov     ss,ax
                mov     eax,cr0                   ;准备返回实模式
                and     al,11111110b
                mov     cr0,eax
                JUMP16  <SEG Real>,<OFFSET Real>
Virtual         ENDP
;----------------------------------------------------------------------------
TempCodeSeg     ENDS
;============================================================================
RDataSeg        SEGMENT PARA USE16                ;实方式数据段
VGDTR           PDesc   <GDTLen-1,>               ;GDT伪描述符
VIDTR           PDesc   <IDTLen-1,>               ;IDT伪描述符
NORVIDTR        PDesc   <3ffh,>                   ;用于保存原IDTR值
SPVar           DW      ?                         ;用于保存实方式下的SP
SSVar           DW      ?                         ;用于保存实方式下的SS
Mess            DB      'Press a key[0,4,B,C,D]:$';提示信息
RDataSeg        ENDS
;----------------------------------------------------------------------------
RCodeSeg        SEGMENT PARA USE16                ;实方式代码段
                ASSUME  CS:RCodeSeg,DS:RDataSeg
;----------------------------------------------------------------------------
Start           PROC
                mov     ax,RDataSeg
                mov     ds,ax
                cld
                call    InitGDT                   ;初始化全局描述符表GDT
                call    InitIDT                   ;初始化中断描述符表IDT
                mov     ax,GKeyLDTSeg
                mov     fs,ax
                mov     cx,GKeyLDNum
                mov     si,OFFSET GLDT
                CALL    InitLDT
                mov     ax,DemoLDTSeg
                mov     fs,ax
                mov     cx,DemoLDNum
                mov     si,OFFSET DLDT
                CALL    InitLDT
                mov     SSVar,ss                  ;保存堆栈指针
                mov     SPVar,sp
                lgdt    QWORD PTR VGDTR           ;装载GDTR
                sidt    QWORD PTR NORVIDTR        ;保存IDTR
                cli                               ;关中断
                lidt    QWORD PTR VIDTR           ;装载IDTR
                mov     eax,cr0
                or      al,1
                mov     cr0,eax
                JUMP16  <TempCode_Sel>,<OFFSET Virtual>
Real:           mov     ax,RDataSeg
                mov     ds,ax
                lss     sp,DWORD PTR SPVar        ;又回到实方式
                lidt    QWORD PTR NORVIDTR
                sti
                mov     ax,4c00h
                int     21h
Start           ENDP
;----------------------------------------------------------------------------
InitGDT         PROC
                push    ds
                mov     ax,GDTSeg
                mov     ds,ax
                mov     cx,GDNum
                mov     si,OFFSET EFFGDT
InitG:          mov     ax,[si].BaseL
                movzx   eax,ax
                shl     eax,4
                shld    edx,eax,16
                mov     WORD PTR [si].BaseL,ax
                mov     BYTE PTR [si].BaseM,dl
                mov     BYTE PTR [si].BaseH,dh
                add     si,SIZE Desc
                loop    InitG
                pop     ds
                mov     bx,16
                mov     ax,GDTSeg
                mul     bx
                mov     WORD PTR VGDTR.Base,ax
                mov     WORD PTR VGDTR.Base+2,dx
                ret
InitGDT         ENDP
;----------------------------------------------------------------------------
InitIDT         PROC
                mov     bx,16
                mov     ax,IDTSeg
                mul     bx
                mov     WORD PTR VIDTR.Base,ax
                mov     WORD PTR VIDTR.Base+2,dx
                ret
InitIDT         ENDP
;----------------------------------------------------------------------------
;入口参数:FS:SI=第一个要初始化的描述符,CX=要初始化的描述符数
;----------------------------------------------------------------------------
InitLDT         PROC
ILDT:           mov     ax,WORD PTR FS:[si].BaseL
                movzx   eax,ax
                shl     eax,4
                shld    edx,eax,16
                mov     WORD PTR fs:[si].BaseL,ax
                mov     BYTE PTR fs:[si].BaseM,dl
                mov     BYTE PTR fs:[si].BaseH,dh
                add     si,SIZE Desc
                loop    ILDT
                ret
InitLDT         ENDP
;----------------------------------------------------------------------------
RCodeSeg        ENDS
                END     Start

2.关于实例七的说明

上述模拟与演示程序的许多内容与实例六相同,下面就各异常处理程序和读键盘任务的实现 作些说明:

(1)除法出错故障处理程序的实现

从源程序可见,除法出错是在执行故意安排的被除数为2000,而除数为2的无符号除指令时 引起的。作为演示,除法出错故障处理程序先显示一条提示信息,然后把存放被除数AX的内 容右移一位,然后就返回。由于除法出错为故障类异常,所以在故障处理结束后,仍执行该 无符号除指令。显然将再次引起同样的故障,仍把被除数右移一位。由于每次处理时都把被 除数减半,所以几次故障后就不发生该故障了。

(2)溢出陷阱处理程序的实现

作为演示的溢出陷阱处理程序较简单。先显示一条提示信息,然后就返回。因为溢出异常为 陷阱类异常,所以在陷阱处理结束后,就直接返回到引起陷阱指令的下一条指令。

(3)段不存在故障处理程序的实现

从源程序可见,段不存在故障是在执行故意安排的把一个选择子送段寄存器GS的指令时引起 的。该选择子索引的描述符中的存在位P被置为0,表示对应段不在内存。在正常情况下,段 不存在故障处理程序要把对应的段装入内存,再把描述符内的P位修改为1,于是,在故障处 理结束后,引起故障的指令可得到顺序执行。为了简单,这里安排的故障处理程序先显示一 条提示信息,然后显示出错码,最后调整堆栈中的返回地址并返回。段不存在故障提供一个 出错码,该故障处理程序利用POP指令把它用堆栈中弹出,这样堆栈指针就指向返回地址。由 于段不存在异常属于故障类异常,所以返回点仍是引起故障的指令。因此,演示程序调整了 堆栈中的返回地址,使其返回到引起故障的指令的下一条指令。

(4)堆栈段出错故障处理程序的实现

引起堆栈出错故障的原因有多种,实例通过执行故意安排的偏移超过段界限的堆栈段访问指 令来模拟堆栈段出错故障的产生。作为演示的堆栈出错故障处理程序比较简单,先显示一条 信息,然后显示出错码,最后调整堆栈中的返回地址并返回。

(5)通用保护障处理程序的实现

引起通用保护故障处理程序的原因有多种,实例通过把一个指向系统段描述符的选择子装入 数据段寄存器GS来模拟通用保护故障的产生。作为演示的通用保护故障处理程序,象上述两 个故障处理程序一样比较简单,先显示一条提示信息,然后显示出错码,最后调整堆栈中的 返回地址并返回,但在废除堆栈中的出错码和调整堆栈中的返回地址时采用了其它方法。

(6)异常处理程序的一般说明

在实例中,通向上述各种异常处理程序的门都是陷阱门。所以,在发生异常而转入这些异常 处理程序时,都不发生任务切换。于是,这些异常处理仍作为演示任务的一部分。
正常情况下,异常处理程序应该注意现场的保护和恢复,但为了简单,作为演示的异常处理 程序没有能够切实地保护现场。注意,这些异常处理程序所采用的处理方法与所模拟的指令 有关,不适用于一般情况。

(7)显示出错代码的过程

实例采用一个过程用于显示出错代码,该过程的入口参数是AX含出错码。利用该过程不仅缩 短程序,而且也用于表现异常处理程序的实现。

(8)读键盘任务的实现

在实例的IDT中,0FFH号门描述符是任务门,指向一个独立的任务。该任务的功能是读键盘, 接收一个指定范围内的字符。演示任务通过指令“INT 0FFH”来调用它,接收一个代表需要 模拟异常的字符。
为了简单,该任务在实模式下读键盘,接收指定范围内的字符。为此,该任务每次经历如下 步骤:(1)转到实模式。此前要作必要的准备,转到实模式后,要恢复必须的实模式下的部 分现场。(2)接收指定的字符。调用DOS功能显示提示信息,调用BIOS中断读键盘,如果在指 定范围内,那么就显示,并保存在约定的数据段中。(3)转回到保护模式,此前也要作必要 的准备。
尽管在任务切换时,自动利用TSS保护和恢复现场,但由于该任务相当于一个读键盘的过程, 所以在开始任务时,还通过堆栈保护必要的现场,在结束任务时恢复现场。请特别注意,安 排在该任务代码段中的IRETD指令之后的转移指令的作用。当执行IRETD指令时,由于NT位 为1,所以按反向链进行任务切换,同时保存各寄存器到当前的TSS,为了下次进入时仍能从 头开始执行此任务,所以在IRETD后加一条转移到该任务开头的指令。

<六>各种转移途径小结

如上所述,中断/异常可引起任务切换、任务内特权级变换和任务内无特权级变换的转移。 至此,任务切换、任务内特权级变换和任务内无特权级变换转移的各种途径已全部列出。

1.任务切换的途径

任务之间切换的途径如下图所示。段间转移指令JMP、段间调用指令CALL、软中断指令INT和 中断返回指令IRET引起的任务切换是主动的任务切换,或者说是当前任务要求的任务切换。 中断和异常(不包括软中断指令)引起的任务切换是被动的任务切换,或者说是不受当前任务 左右的任务切换。
伴随着任务切换,特权级当然可能发生变换。只要任务切换发生,这种特权级的变换取决于 目标任务,而与当前任务无关。

2.任务内特权级变换的途径

任务内特权级变换的途径如下图所示。图中特权级m是外层特权级,特权级n是内层特权级。 通常RET与CALL对应;IRET与INT、中断/异常对应。但也可以通过在堆栈中建立合适环境的 手段,使RET或IRET从内层特权级变换到外层特权级。

3.任务内相同特权级转移的途径

任务内相同特权级转移的途径如下图所示。由图可见,任务内相同特权级转移的途径多种 多样。

参考资料 书        名 出  版  社 作    者
《保护方式下的80386及其编程》 清华大学出版社 周明德主编    
《80X86汇编语言程序设计教程》 清华大学出版社 扬季文主编    
《32位系统软件编程指南》      电子工业出版社 程荷、武航翻译